﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: SqlQuery.cs
//
//  Description: A SQL query with standardized parameter behaviour.
// 
//--------------------------------------------------------------------------

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using Chili.Opf3.Storages;

namespace Chili.Opf3.Query
{
	/// <summary>Allows to specify directly storage dependent Sql queries.</summary>
	/// <remarks>
	/// 	<para>SqlQuery allows to specify directly storage dependent queries that are then
	///     executed on the storage without any kind of parsing. This type of query is used to
	///     create complex joins or complex queries.</para>
	/// 	<para>You should avoid to use SqlQuery in your code. Sometimes it could be
	///     better to create a stored procedure or an updatable view (in the storage). It's (in
	///     most cases) also faster to create those complex queries directly in the
	///     storage.</para>
	/// 	<para>If you have to use this type of query in your application use it for example
	///     in specialized classes that do only execute queries (and NOT directly in your UI).
	///     You could for example derive from
	///     <see cref="Opf3.ObjectSearcher{T}">ObjectSearcher</see> and create a customized
	///     searcher class that uses such queries.</para>
	/// 	<para>When setting parameter have to use the {0}, {1}, ... syntax as shown in the
	///     example below.</para>
	/// </remarks>
	/// <example>
	///     The following example shows how to use a SqlQuery in a customized searcher class (a
	///     class that derives from ObjectSearcher). 
	///     <code lang="CS">
	/// // A customized User searcher.
	/// public class UserSearcher : ObjectSearcher&lt;User&gt;
	/// {
	///     public UserSearcher(ObjectContext context) : base(context)
	///     {
	///     }
	///  
	///     // Returns an ObjectSet with all user that have calls.
	///     public ObjectSet&lt;User&gt; FindWithCalls(string userName)
	///     {
	///         // Extended query.
	///         SqlQuery query = new SqlQuery(
	///             "select distinct u.* from [USER] u, CALLS c where u.ID = c.USER_ID " +
	///             "and u.NAME = {0};", userName);
	///  
	///         return Context.GetObjectSet&lt;User&gt;(query);
	///     }
	///  
	///     // Returns an ObjectReader with all users that have calls.
	///     public ObjectReader&lt;User&gt; FindWithCallsReader()
	///     {
	///         // Extended query.
	///         SqlQuery query = new SqlQuery(
	///             "select distinct u.* from [USER] u, CALLS c where u.ID = c.USER_ID " +
	///             "and u.NAME = {0};", userName);
	///  
	///         return Context.GetObjectReader&lt;User&gt;(query);
	///     }
	/// }
	///     </code>
	/// </example>
	[Serializable]
	public sealed class SqlQuery : IQuery
	{
		// TODO: Add routines like: AddParameter, ClearParameter and the possibility to change the command. 
		// TODO: A constructor without parameters has also to be added.
		private string _command;
		private IList<object> _parameters;
		private int _timeout = 30;
		private QueryTypes _queryType = QueryTypes.Unknown;
		private CommandType _type = CommandType.Text;

		/// <summary>Creates a new instance of the <see cref="SqlQuery">SqlQuery Class</see>.</summary>
		/// <param name="command">Command that is executed on the storage.</param>
		/// <exception cref="ArgumentNullException">Command is null.</exception>
		public SqlQuery(string command)
		{
			if (command == null)
				throw new ArgumentNullException("command");

			_command = command;
			GetQueryType();
		}

		/// <summary>Creates a new instance of the <see cref="SqlQuery">SqlQuery Class</see>.</summary>
		/// <param name="command">Command that is executed on the storage.</param>
		/// <param name="parameters">Parameter that are passed for the command.</param>
		/// <exception cref="ArgumentNullException">Command is null.</exception>
		public SqlQuery(string command, params object[] parameters)
		{
			_command = command;
			_parameters = new List<object>(parameters);

			GetQueryType();
		}

		/// <summary>Specifies the command of the <see cref="SqlQuery">SqlQuery</see>.</summary>
		/// <value>A string that represents the query.</value>
		internal string Command
		{
			get { return _command; }
		}

		internal IList<object> Parameters
		{
			get { return _parameters; }
		}

		/// <summary>
		/// Gets or sets the command type of the <see cref="SqlQuery">SqlQuery</see>.
		/// </summary>
		public CommandType CommandType
		{
			get { return _type; }
			set { _type = value; }
		}

		/// <summary>
		/// Tries to get the type of the <see cref="SqlQuery">SqlQuery</see>.
		/// </summary>
		private void GetQueryType()
		{
			string command = _command.ToLower().Trim();
			if (command.StartsWith("select"))
				_queryType = QueryTypes.Load;
			else if (command.StartsWith("update"))
				_queryType = QueryTypes.Update;
			else if (command.StartsWith("insert"))
				_queryType = QueryTypes.Insert;
			else if (command.StartsWith("delete"))
				_queryType = QueryTypes.Delete;
			else
				_queryType = QueryTypes.Unknown;
		}

		#region IQuery Members

		/// <summary>
		/// Converts the <see cref="IQuery">IQuery</see> to an
		/// <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see> that is then executed on a
		/// specific <see cref="Opf3.Storages.IStorage">storage</see>.
		/// </summary>
		/// <returns>
		/// A new <see cref="CompiledQuery">CompiledQuery</see> that has been generated using this instance.
		/// </returns>
		/// <param name="storage">
		/// Class that implements the <see cref="Opf3.Storages.IStorage">IStorage</see>
		/// interface. The query is executed on that storage.
		/// </param>
		/// <exception cref="ArgumentNullException">Storage is null.</exception>
		public CompiledQuery ToCompiledQuery(IStorage storage)
		{
			if (storage == null)
				throw new ArgumentNullException("storage");

			// Use the IStorageCommandBuilder to compile this query.
			IStorageCommandBuilder scb = storage.StorageCommandBuilders.GetStorageCommandBuilder(this);
			return scb.ToCompiledQuery(storage, this);
		}

		/// <summary>
		/// Specifies the wait time before terminating the attempt to execute a command and generating an error.
		/// </summary>
		/// <value>An integer that specifies the wait time in seconds.</value>
		/// <exception cref="ArgumentException">Value is lower then zero.</exception>
		public int Timeout
		{
			get { return _timeout; }
			set 
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("value");

				_timeout = value; 
			}
		}

		/// <summary>
		/// Returns the <see cref="Opf3.Query.QueryTypes">QueryType</see> of the SqlQuery.
		/// </summary>
		/// <value>
		/// A member of the <see cref="Opf3.Query.QueryTypes">QueryType</see>
		/// enumeration.
		/// </value>
		public QueryTypes QueryType
		{
			get { return _queryType; }
		}

		#endregion
	}
}
